﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Ast.Framework.Caching;
using Ast.Framework.Environment.Extensions;
using Ast.Framework.Environment.Extensions.Events;
using Ast.Framework.FileSystems.Dependencies;
using Ast.Framework.FileSystems.Paths;
using Ast.Framework.FileSystems.SiteFolders;
using Ast.Framework.Forms;
using Ast.Core.Forms;
using Ast.Framework.Logging;
using Castle.Core.Logging;

namespace Ast
{
    [FormMetadata(FormType.Extension, IsTabForm = true, Order = -1)]
    public partial class ExtensionForm : Form, IExtensionEvents
    {
        private readonly IExtensionLoaderCoordinator _extensionLoaderCoordinator;
        private readonly IDependenciesFolder _dependenciesFolder;
        private readonly IExtensionMonitoringCoordinator _extensionMonitoringCoordinator;
        private readonly ICacheManager _cacheManager;
        private readonly IPathProvider _pathProvider;
        private readonly IModuleFolderRoot _moduleFolderRoot;
        private readonly ILoggingMessageQueue _loggingMessageQueue;

        private readonly ConcurrentDictionary<string, ExtensionInstance> _extensionInstances;
        private readonly Type _loaderType = typeof (ExtensionLoader);

        private IEnumerable<DependencyDescriptor> _descriptors;
        private readonly object _syncLock = new object();

        private CancellationTokenSource _watcherTokenSource;

        public ExtensionForm(IExtensionLoaderCoordinator extensionLoaderCoordinator,
            IDependenciesFolder dependenciesFolder,
            IExtensionMonitoringCoordinator extensionMonitoringCoordinator,
            ICacheManager cacheManager,
            IPathProvider pathProvider,
            IModuleFolderRoot moduleFolderRoot,
            ILoggingMessageQueue loggingMessageQueue)
        {
            _extensionLoaderCoordinator = extensionLoaderCoordinator;
            _dependenciesFolder = dependenciesFolder;
            _extensionMonitoringCoordinator = extensionMonitoringCoordinator;
            _cacheManager = cacheManager;
            _pathProvider = pathProvider;
            _moduleFolderRoot = moduleFolderRoot;
            _loggingMessageQueue = loggingMessageQueue;

            InitializeComponent();

            _extensionInstances = new ConcurrentDictionary<string, ExtensionInstance>();

            Logger = NullLogger.Instance;
        }

        public ILogger Logger { get; set; }

        private async void ExtensionForm_Load(object sender, EventArgs e)
        {
            await Task.Run(() =>
            {
                // trigger first build
                BuildCurrent();

                // setup file watcher
                extensionWatcher.Path = _moduleFolderRoot.Path;
                _watcherTokenSource = new CancellationTokenSource();
                Task.Run(() =>
                {
                    lock (_watcherTokenSource)
                    {
                        while (true)
                        {
                            if (_watcherTokenSource.IsCancellationRequested) break;
                            var result = extensionWatcher.WaitForChanged(WatcherChangeTypes.All, 100);

                            if (!result.TimedOut)
                            {
                                while (!result.TimedOut)
                                {
                                    // wait for another one sec to see if any more changes happening
                                    result = extensionWatcher.WaitForChanged(WatcherChangeTypes.All, 1000);
                                }

                                // build
                                MonitorExtensions();
                                BuildCurrent();
                            }
                        }
                    }
                }, _watcherTokenSource.Token).ConfigureAwait(false);
            }).ConfigureAwait(false);
        }

        private void ExtensionForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            // close all extensions
            while (!_extensionInstances.IsEmpty)
            {
                StopExtension(_extensionInstances.Keys.First()).Wait();
            }

            // exist watcher
            _watcherTokenSource.Cancel();
            lock (_watcherTokenSource)
            {
                extensionWatcher.Dispose();
            }
        }

        private async void btnLoad_Click(object sender, EventArgs e)
        {
            if (lvExtensions.SelectedItems.Count == 0) return;
            await StartExtension(((ExtensionViewItem) lvExtensions.SelectedItems[0].Tag).Name).ConfigureAwait(false);
        }

        private async void btnUnload_Click(object sender, EventArgs e)
        {
            if (lvExtensions.SelectedItems.Count == 0) return;
            await StopExtension(((ExtensionViewItem) lvExtensions.SelectedItems[0].Tag).Name).ConfigureAwait(false);
        }

        private void btnSetAuto_Click(object sender, EventArgs e)
        {
            if (lvExtensions.SelectedItems.Count == 0) return;
            var extensionViewItem = (ExtensionViewItem) lvExtensions.SelectedItems[0].Tag;
            extensionViewItem.AutoLoad = !extensionViewItem.AutoLoad;
            UpdateViewItem(extensionViewItem, lvExtensions.SelectedItems[0]);
        }

        private IEnumerable<DependencyDescriptor> BuildCurrent()
        {
            if (_descriptors == null)
            {
                lock (_syncLock)
                {
                    if (_descriptors == null)
                    {
                        SetupExtensions();
                        MonitorExtensions();
                        _descriptors = _dependenciesFolder.LoadDescriptors();
                    }
                }
            }

            return _descriptors;
        }

        private void SetupExtensions()
        {
            _extensionLoaderCoordinator.SetupExtensions();
        }

        private void MonitorExtensions()
        {
            // This is a "fake" cache entry to allow the extension loader coordinator
            // notify us (by resetting _descriptors to "null") when an extension has changed
            // on disk, and we need to reload new/updated extensions.
            _cacheManager.Get("Extensions",
                ctx =>
                {
                    _extensionMonitoringCoordinator.MonitorExtensions(ctx.Monitor);
                    _descriptors = null;
                    return "";
                });
        }

        private async Task StartExtension(string moduleName)
        {
            var instance = await Task.Run(() =>
            {
                if (!_extensionInstances.TryAdd(moduleName, new ExtensionInstance {Name = moduleName}))
                {
                    Logger.WarnFormat("already loaded {0}", moduleName);
                    return null;
                }

                ExtensionInstance extensionInstance;
                if (!_extensionInstances.TryGetValue(moduleName, out extensionInstance)) return null;

                // status
                UpdateStatus(moduleName, ExtensionStatus.Initializing);

                // create app domain & loader
                var info = new AppDomainSetup {ShadowCopyFiles = "true"};
                extensionInstance.AppDomain = AppDomain.CreateDomain(extensionInstance.Name, null, info);
                extensionInstance.Loader = (ExtensionLoader) extensionInstance.AppDomain.CreateInstanceAndUnwrap(_loaderType.Assembly.FullName, _loaderType.FullName);

                // initialize
                Logger.InfoFormat("initializing {0}", moduleName);
                var extensionInitializeParameters = new ExtensionInitializeParameters {ExtensionName = moduleName, HostLoggingMessageQueue = _loggingMessageQueue};
                if (!extensionInstance.Loader.Initialize(extensionInitializeParameters))
                {
                    // status
                    UpdateStatus(moduleName, ExtensionStatus.Error);

                    Logger.ErrorFormat("failed to initialize {0}", moduleName);
                    AppDomain.Unload(extensionInstance.AppDomain);
                    _extensionInstances.TryRemove(moduleName, out extensionInstance);
                    return null;
                }

                // all good
                return extensionInstance;
            }).ConfigureAwait(false);

            if (instance != null)
            {
                try
                {
                    var result = await StartExtensionLoadTask(instance).ConfigureAwait(false); // running
                    if (result != null && result.CanUnload)
                    {
                        await StopExtension(instance.Name).ConfigureAwait(false); // unload
                    }
                }
                catch (Exception ex)
                {
                    Logger.ErrorFormat(ex, "failed to load {0}", moduleName);
                    AppDomain.Unload(instance.AppDomain);
                    _extensionInstances.TryRemove(moduleName, out instance);

                    // status
                    UpdateStatus(moduleName, ExtensionStatus.Error);
                }
            }
        }

        private async Task<ExtensionLoadResult> StartExtensionLoadTask(ExtensionInstance extensionInstance)
        {
            var tcs = new TaskCompletionSource<ExtensionLoadResult>();
            extensionInstance.Thread = new Thread(() =>
            {
                try
                {
                    Logger.InfoFormat("loading {0}", extensionInstance.Name);
                    var loadResult = extensionInstance.Loader.Load();
                    tcs.SetResult(loadResult);
                }
                catch (Exception ex)
                {
                    tcs.SetException(ex);
                }
            });
            extensionInstance.Thread.SetApartmentState(ApartmentState.STA);
            extensionInstance.Thread.Start();

            // status
            UpdateStatus(extensionInstance.Name, ExtensionStatus.Running);

            return await tcs.Task.ConfigureAwait(false);
        }

        private async Task StopExtension(string moduleName)
        {
            await Task.Run(() =>
            {
                ExtensionInstance extensionInstance;
                if (!_extensionInstances.TryGetValue(moduleName, out extensionInstance)) return;
                if (extensionInstance.Thread == null || extensionInstance.Thread.ThreadState == ThreadState.Unstarted) return; // maybe still initializing
                if (!_extensionInstances.TryRemove(moduleName, out extensionInstance)) return;

                // status
                UpdateStatus(extensionInstance.Name, ExtensionStatus.Stopping);

                // unload
                Logger.InfoFormat("stopping {0}", moduleName);
                extensionInstance.Loader.Unload();
                if (!extensionInstance.Thread.Join(TimeSpan.FromSeconds(5)))
                {
                    Logger.WarnFormat("forced {0} to stop", moduleName);
                }
                try
                {
                    AppDomain.Unload(extensionInstance.AppDomain);
                    Logger.InfoFormat("stopped {0}", moduleName);

                    // status
                    UpdateStatus(extensionInstance.Name, ExtensionStatus.Stopped);
                }
                catch (Exception ex)
                {
                    Logger.ErrorFormat(ex, "error occurred when unloading domain {0}", moduleName);

                    // status
                    UpdateStatus(extensionInstance.Name, ExtensionStatus.Error);
                }
            }).ConfigureAwait(false);
        }

        private void UpdateStatus(string extensionName, ExtensionStatus status)
        {
            BeginInvoke(new Action(() =>
            {
                var item = lvExtensions.Items.Find(extensionName, false).FirstOrDefault();
                if (item == null) return;

                var extensionViewItem = (ExtensionViewItem) item.Tag;
                extensionViewItem.Status = status;
                item.SubItems[5].Text = status.ToString(); // update status

                if (status == ExtensionStatus.Stopping)
                {
                    // clear back color if require reload
                    if (extensionViewItem.RequiredReload)
                    {
                        extensionViewItem.RequiredReload = false;
                        item.BackColor = BackColor;
                    }

                    // remove item if removed
                    if (extensionViewItem.IsRemoved)
                    {
                        lvExtensions.Items.Remove(item);
                    }
                }
            }));
        }

        private bool IsExtensionLoaded(string extensionName)
        {
            return _extensionInstances.ContainsKey(extensionName);
        }

        private ListViewItem FindViewItem(string extensionName)
        {
            var item = lvExtensions.Items.Find(extensionName, false).FirstOrDefault();
            return item;
        }

        private ListViewItem CreateViewItem(ExtensionViewItem extensionViewItem)
        {
            var viewItem = new ListViewItem(new[] {extensionViewItem.Name, string.Empty, string.Empty, string.Empty, string.Empty, string.Empty})
            {
                Name = extensionViewItem.Name,
            };
            UpdateViewItem(extensionViewItem, viewItem);
            return viewItem;
        }

        private void UpdateViewItem(ExtensionViewItem extensionViewItem, ListViewItem viewItem)
        {
            viewItem.Tag = extensionViewItem;
            viewItem.SubItems[1].Text = extensionViewItem.Loader;
            viewItem.SubItems[2].Text = extensionViewItem.Location;
            viewItem.SubItems[3].Text = extensionViewItem.TimeStamp.ToLocalTime().ToString();
            viewItem.SubItems[4].Text = extensionViewItem.AutoLoad.ToString();
            viewItem.SubItems[5].Text = extensionViewItem.Status.ToString();
            if (extensionViewItem.RequiredReload) viewItem.BackColor = Color.Yellow;
            if (extensionViewItem.IsRemoved) viewItem.BackColor = Color.Red;
        }

        public void OnActivated(ExtensionLoadingContext context, DependencyDescriptor extensionDescriptor)
        {
            var extenionViewItem = new ExtensionViewItem
            {
                Name = extensionDescriptor.Name,
                Loader = extensionDescriptor.LoaderName,
                Location = extensionDescriptor.Path,
                Status = ExtensionStatus.Ready,
                TimeStamp = context.AvailableExtensionsProbes[extensionDescriptor.Name].First(x => x.Loader.Name == extensionDescriptor.LoaderName).PathDependencies.Max(path => _pathProvider.GetFileLastWriteTimeUtc(path)),
                AutoLoad = false
            };

            // update view
            Invoke(new Action(() =>
            {
                var viewItem = FindViewItem(extensionDescriptor.Name);
                if (viewItem == null)
                {
                    // create
                    viewItem = CreateViewItem(extenionViewItem);
                    lvExtensions.Items.Add(viewItem);
                }
                else
                {
                    var oldExtensionViewItem = (ExtensionViewItem) viewItem.Tag;
                    if (!IsExtensionLoaded(extensionDescriptor.Name))
                    {
                        // update
                        extenionViewItem.AutoLoad = oldExtensionViewItem.AutoLoad;
                        UpdateViewItem(extenionViewItem, viewItem);

                        // auto load
                        if (extenionViewItem.AutoLoad)
                        {
                            Task.Run(async () =>
                            {
                                try
                                {
                                    await StartExtension(extensionDescriptor.Name).ConfigureAwait(false);
                                }
                                catch (Exception ex)
                                {
                                    Logger.ErrorFormat(ex, "failed to auto start extension {0}", extensionDescriptor.Name);
                                }
                            }).ConfigureAwait(false);
                        }
                    }
                    else
                    {
                        // extension is loaded
                        if (oldExtensionViewItem.IsRemoved ||
                            oldExtensionViewItem.Loader != extenionViewItem.Loader ||
                            oldExtensionViewItem.Location != extenionViewItem.Location ||
                            oldExtensionViewItem.TimeStamp < extenionViewItem.TimeStamp)
                        {
                            // need to reload
                            extenionViewItem.RequiredReload = true;
                            extenionViewItem.IsRemoved = false;
                            extenionViewItem.Status = oldExtensionViewItem.Status;
                            extenionViewItem.AutoLoad = oldExtensionViewItem.AutoLoad;
                            UpdateViewItem(extenionViewItem, viewItem);

                            // auto load
                            if (extenionViewItem.AutoLoad)
                            {
                                Task.Run(async () =>
                                {
                                    try
                                    {
                                        await StopExtension(extensionDescriptor.Name).ConfigureAwait(false);
                                        await StartExtension(extensionDescriptor.Name).ConfigureAwait(false);
                                    }
                                    catch (Exception ex)
                                    {
                                        Logger.ErrorFormat(ex, "failed to auto start extension {0}", extensionDescriptor.Name);
                                    }
                                });
                            }
                        }
                    }
                }
                lvExtensions.AutoResizeColumns();
            }));
        }

        public void OnDeactivated(ExtensionLoadingContext context, DependencyDescriptor extensionDescriptor)
        {
        }

        public void OnRemoved(ExtensionLoadingContext context, DependencyDescriptor dependencyDescriptor)
        {
            // update view
            Invoke(new Action(() =>
            {
                var viewItem = FindViewItem(dependencyDescriptor.Name);
                if (viewItem != null)
                {
                    var extensionViewItem = (ExtensionViewItem) viewItem.Tag;

                    if (!IsExtensionLoaded(dependencyDescriptor.Name))
                    {
                        // remove
                        lvExtensions.Items.Remove(viewItem);
                    }
                    else
                    {
                        // extension is loaded
                        extensionViewItem.IsRemoved = true;
                        UpdateViewItem(extensionViewItem, viewItem);
                    }
                }
            }));
        }
    }

    public enum ExtensionStatus
    {
        Ready,
        Initializing,
        Running,
        Stopping,
        Stopped,
        Error
    }

    public class ExtensionInstance
    {
        public string Name { get; set; }
        public AppDomain AppDomain { get; set; }
        public ExtensionLoader Loader { get; set; }
        public Thread Thread { get; set; }
    }

    public class ExtensionViewItem
    {
        public string Name { get; set; }
        public string Loader { get; set; }
        public string Location { get; set; }
        public DateTime TimeStamp { get; set; }
        public ExtensionStatus Status { get; set; }
        public bool RequiredReload { get; set; }
        public bool IsRemoved { get; set; }
        public bool AutoLoad { get; set; }
    }
}